package com.castle.fortress.admin.flowable.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.castle.fortress.admin.flowable.enums.FlowableApprovalTypeEnum;
import com.castle.fortress.admin.flowable.enums.FlowableApproveModeEnum;
import com.castle.fortress.admin.flowable.enums.FlowableNodeTypeEnum;
import com.castle.fortress.common.utils.CommonUtil;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Process;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 流程工具类
 * @author castle
 */
public class FlowableUtils {
    public static String jsonToXml(String actKey, String processName, JSONObject process){
        Document basic = createRootDoc(actKey,processName,process);
        Element processRoot = XmlUtil.getElement(XmlUtil.getRootElement(basic),"process");
        Element startEle = createElement(process,processRoot,basic);
        JSONObject nodeData = process.getJSONObject("node");
        Element ele=null;
        while(nodeData!=null){
            //条件分支
            if(FlowableNodeTypeEnum.CONDITION.getName().equals(nodeData.getStr("type"))){
                ele =createConditionElement(nodeData,processRoot,basic);
            }else{
                ele = createElement(nodeData,processRoot,basic);
            }
            nodeData=nodeData.getJSONObject("node");
            if(nodeData!=null && FlowableNodeTypeEnum.EMPTY.getName().equals(nodeData.getStr("type"))){
                nodeData=nodeData.getJSONObject("node");
            }
        }
        ele=(ele==null?startEle:ele);
        if(!"exclusiveGateway".equals(ele.getTagName())){
            createEndElement(ele,processRoot,basic);
        }
        String result= XmlUtil.toStr(basic);
        return result;
    }

    public static Document createRootDoc(String actKey,String processName,JSONObject process){
        String xmlStr ="<definitions\n" +
                "    xmlns=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"\n" +
                "    xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\"\n" +
                "    xmlns:omgdc=\"http://www.omg.org/spec/DD/20100524/DC\"\n" +
                "    xmlns:omgdi=\"http://www.omg.org/spec/DD/20100524/DI\"\n" +
                "    xmlns:flowable=\"http://flowable.org/bpmn\" targetNamespace=\"http://flowable.org/bpmn20\"><process id=\""+actKey+"\" name=\""+processName+"\" isExecutable=\"true\">" +
                "<extensionElements>\n" +
                "    <flowable:executionListener\n" +
                "       delegateExpression=\"${fortressHolidayCategaryEndListener}\""+
                "      event=\"end\" />\n" +
                "</extensionElements>"+
                "</process></definitions>";
        Document root = XmlUtil.parseXml(xmlStr);
        return root;
    }

    /**
     * 依据类型创建节点
     * @param data
     * @return
     */
    public static Element createElement(JSONObject data, Element processRoot,Document doc){
        Element ele=null;
        //root节点
        if(FlowableNodeTypeEnum.ROOT.getName().equals(data.getStr("type"))){
            ele = doc.createElement("startEvent");
            ele.setAttribute("id","NODE_"+data.getStr("id"));
            //审批节点
        }else if(FlowableNodeTypeEnum.SP.getName().equals(data.getStr("type"))){
            ele = doc.createElement("userTask");
            ele.setAttribute("id","NODE_"+data.getStr("id"));
            ele.setAttribute("name",data.getStr("name"));
            Element extensionElements = doc.createElement("extensionElements");
            JSONObject props = data.getJSONObject("props");
            JSONObject targetObj = props.getJSONObject("targetObj");
            //多人审批
            boolean multiple=false;
            //审批人为空
            boolean emptyFlag=false;
            //是否是连续主管
            boolean isLeadersLoop = false;
            //审批人类型-指定审批人
            if(FlowableApprovalTypeEnum.ASSIGN_USER.getName().equals(props.getStr("type"))) {
                JSONArray u = targetObj.getJSONArray("objs");
                ele.setAttribute("flowable:assignee", u.get(0, JSONObject.class).getStr("id"));
                ele.setAttribute("flowable:fortress_userType", "assign");
            //指定多个审批人
            }else if(FlowableApprovalTypeEnum.ASSIGN_USERS.getName().equals(props.getStr("type"))){
                emptyFlag=true;
                multiple=true;
                ele.setAttribute("flowable:fortress_userType","assigns");
                ele.setAttribute("flowable:assignee","${_fortress_assignees_multiple_item}");
                JSONArray u = targetObj.getJSONArray("objs");
                StringBuilder sb = new StringBuilder();
                for(int i=0;i<u.size();i++){
                    sb.append(u.get(i, JSONObject.class).getStr("id")+",");
                }
                ele.setAttribute("flowable:fortress_users",sb.toString());
                //发起人自选
            }else if(FlowableApprovalTypeEnum.SELF_SELECT.getName().equals(props.getStr("type"))){
                emptyFlag=true;
                if(targetObj.getBool("multiple")){
                    multiple=true;
                    ele.setAttribute("flowable:fortress_userType","users");
                    ele.setAttribute("flowable:assignee","${_fortress_assignees_multiple_item}");
                }else{
                    ele.setAttribute("flowable:fortress_userType","single");
                    ele.setAttribute("flowable:assignee","${_fortress_self_assignee_single_"+ele.getAttribute("id")+"}");
                }
                //连续多级主管
            }else if(FlowableApprovalTypeEnum.LEADER_TOP.getName().equals(props.getStr("type"))){
                multiple=true;
                emptyFlag=true;
                isLeadersLoop=true;
                ele.setAttribute("flowable:fortress_userType","leaders");
                ele.setAttribute("flowable:assignee","${_fortress_assignees_multiple_item}");
                //TOP/LEAVE
                ele.setAttribute("flowable:fortress_endCondition",props.getStr("endCondition"));
                if("LEAVE".equals(props.getStr("endCondition"))){
                    ele.setAttribute("flowable:fortress_leaderLevel",props.getStr("leaderLevel"));
                }
                //指定主管
            }else if(FlowableApprovalTypeEnum.LEADER.getName().equals(props.getStr("type"))){
                multiple=true;
                emptyFlag=true;
                ele.setAttribute("flowable:fortress_userType","leader");
                ele.setAttribute("flowable:assignee","${_fortress_assignees_multiple_item}");
                ele.setAttribute("flowable:fortress_endCondition","SINGLE");
                ele.setAttribute("flowable:fortress_leaderLevel",props.getStr("leaderLevel"));
                //角色
            }else if(FlowableApprovalTypeEnum.ROLE.getName().equals(props.getStr("type"))){
                multiple=true;
                emptyFlag=true;
                ele.setAttribute("flowable:fortress_userType","roles");
                JSONArray roleArray= targetObj.getJSONArray("roles");
                StringBuilder roles = new StringBuilder();
                if(roleArray!=null && roleArray.size()>0){
                    for(int i=0;i<roleArray.size();i++){
                        roles.append(roleArray.getJSONObject(i).getStr("id")+",");
                    }
                }
                ele.setAttribute("flowable:assignee","${_fortress_assignees_multiple_item}");
                ele.setAttribute("flowable:fortress_roles",roles.toString());
            }else{
                ele.setAttribute("flowable:assignee","${_fortress_assignees_self}");
                ele.setAttribute("flowable:fortress_userType","self");
            }
            if(emptyFlag){
                Element taskListener = doc.createElement("flowable:taskListener");
                taskListener.setAttribute("event","create");
                taskListener.setAttribute("delegateExpression","${fortressUserTaskCreateListener}");
                Element emptyField = doc.createElement("flowable:field");
                emptyField.setAttribute("name","emptyType");
                //自动通过 / 转交到指定人员
                String userEmpty = props.getStr("userEmpty");
                if("TO_USER".equals(userEmpty)){
                    JSONArray delivers = targetObj.getJSONArray("delivers");
                    if(delivers!=null && delivers.size()>0){
                        emptyField.setAttribute("stringValue",delivers.get(0,JSONObject.class).getStr("id"));
                    }
                }else{
                    emptyField.setAttribute("stringValue","TO_PASS");
                }
                taskListener.appendChild(emptyField);
                extensionElements.appendChild(taskListener);
                ele.appendChild(extensionElements);
            }
            Element refuseTaskListener = doc.createElement("flowable:taskListener");
            refuseTaskListener.setAttribute("event","complete");
            refuseTaskListener.setAttribute("delegateExpression","${fortressUserTaskCompleteListener}");
            //结束/返回上一个审批节点
            Element refuseField = doc.createElement("flowable:field");
            refuseField.setAttribute("name","refuseType");
            //默认是继续流转
            String refuse = props.getStr("refuse");
            if("PRE_NODE".equals(refuse)){
                refuseField.setAttribute("stringValue","PRE_NODE");
            }else if("ENDED_PRO".equals(refuse)){
                refuseField.setAttribute("stringValue","ENDED_PRO");
            }else if("TO PASS".equals(refuse)){
                refuseField.setAttribute("stringValue","TO_PASS");
            }else{
                refuseField.setAttribute("stringValue","ANY_NODE");
            }
            refuseTaskListener.appendChild(refuseField);
            //连续主管
            if(isLeadersLoop){
                Element leadersLoopField = doc.createElement("flowable:field");
                leadersLoopField.setAttribute("name","leadersLoop");
                if(FlowableApproveModeEnum.OR.getName().equals(props.getStr("mode"))){
                    //或签
                    leadersLoopField.setAttribute("stringValue","APPROVE_OR");
                }else{
                    //会签
                    leadersLoopField.setAttribute("stringValue","APPROVE_AND");
                }
                refuseTaskListener.appendChild(leadersLoopField);
            }
            extensionElements.appendChild(refuseTaskListener);
            ele.appendChild(extensionElements);
            if(multiple){
                Element multiInstanceLoop = doc.createElement("multiInstanceLoopCharacteristics");
                multiInstanceLoop.setAttribute("flowable:collection","${_fortress_assignees_multiple_"+ele.getAttribute("id")+"}");
                multiInstanceLoop.setAttribute("flowable:elementVariable","_fortress_assignees_multiple_item");
                Element completionCondition = doc.createElement("completionCondition");
                if(FlowableApproveModeEnum.NEXT.getName().equals(props.getStr("mode"))){
                    multiInstanceLoop.setAttribute("isSequential","true");
                }else{
                    multiInstanceLoop.setAttribute("isSequential","false");
                }
                if(FlowableApproveModeEnum.OR.getName().equals(props.getStr("mode"))){
                    completionCondition.setTextContent("${nrOfCompletedInstances > 0 }");
                }else{
                    completionCondition.setTextContent("${nrOfCompletedInstances == nrOfInstances }");
                }
                multiInstanceLoop.appendChild(completionCondition);
                ele.appendChild(multiInstanceLoop);
            }
            //抄送节点
        }else if(FlowableNodeTypeEnum.CS.getName().equals(data.getStr("type"))){
            ele = doc.createElement("serviceTask");
            ele.setAttribute("id","NODE_"+data.getStr("id"));
            ele.setAttribute("name",data.getStr("name"));
            ele.setAttribute("flowable:delegateExpression","${fortressRecipientsDelegate}");
            JSONObject props = data.getJSONObject("props");
            JSONArray objs = props.getJSONObject("targetObj").getJSONArray("objs");
            Element extensionElements = doc.createElement("extensionElements");
            //是否允许审批节点自选抄送人： 00 不允许 01 允许
            String csType="00";
            if(props.getJSONObject("targetObj").containsKey("csType")){
                csType = props.getJSONObject("targetObj").getStr("csType");
            }
            Element fieldCsType = doc.createElement("flowable:field");
            fieldCsType.setAttribute("name","csType");
            Element fieldCsString = doc.createElement("flowable:string");
            fieldCsString.setTextContent(csType);
            fieldCsType.appendChild(fieldCsString);
            extensionElements.appendChild(fieldCsType);
            //系统预设的抄送人和抄送部门
            if(objs!=null && objs.size()>0){
                StringBuilder depts = new StringBuilder();
                StringBuilder users = new StringBuilder();
                for(int i = 0;i<objs.size();i++){
                    JSONObject o = objs.getJSONObject(i);
                    if("00".equals(o.getStr("type"))){
                        depts.append(o.getStr("id")+",");
                    }else{
                        users.append(o.getStr("id")+",");
                    }
                }
                if(StrUtil.isNotEmpty(depts.toString())){
                    Element fieldDepts = doc.createElement("flowable:field");
                    fieldDepts.setAttribute("name","depts");
                    Element fieldDeptsString = doc.createElement("flowable:string");
                    fieldDeptsString.setTextContent(depts.substring(0,depts.toString().length()-1));
                    fieldDepts.appendChild(fieldDeptsString);
                    extensionElements.appendChild(fieldDepts);
                }
                if(StrUtil.isNotEmpty(users.toString())){
                    Element fieldUsers = doc.createElement("flowable:field");
                    fieldUsers.setAttribute("name","users");
                    Element fieldUsersString = doc.createElement("flowable:string");
                    fieldUsersString.setTextContent(users.substring(0,users.toString().length()-1));
                    fieldUsers.appendChild(fieldUsersString);
                    extensionElements.appendChild(fieldUsers);
                }
            }
            ele.appendChild(extensionElements);
        }
        processRoot.appendChild(ele);
        //是否有下个节点
        if(data.getJSONObject("node")!=null){
            JSONObject nextNode = data.getJSONObject("node");
            //连接线
            Element sequenceFlow = doc.createElement("sequenceFlow");
            sequenceFlow.setAttribute("id","FLOW_"+nextNode.getStr("pid")+"_"+nextNode.getStr("id"));
            sequenceFlow.setAttribute("sourceRef",ele.getAttribute("id"));
            sequenceFlow.setAttribute("targetRef","NODE_"+nextNode.getStr("id"));
            processRoot.appendChild(sequenceFlow);
        }
        return ele;
    }

    /**
     * 创建条件分支 排他网关
     * @param data
     * @param processRoot
     * @param doc
     * @return
     */
    public static Element createConditionElement(JSONObject data, Element processRoot,Document doc){
        //排他网关
        Element eleGateway=doc.createElement("exclusiveGateway");
        String exclusiveGatewayId = data.getStr("id");
        eleGateway.setAttribute("id","NODE_"+exclusiveGatewayId);
        processRoot.appendChild(eleGateway);
        List<String> sourceRefIds = new ArrayList<>();
        //条件数组
        JSONArray conditionsArray= data.getJSONArray("conditions");
        for(int i=0;i<conditionsArray.size();i++){
            //条件组节点
            JSONObject conditions = conditionsArray.get(i,JSONObject.class);
            //条件组表达式
            StringBuilder groupConditionExp=new StringBuilder("${");
            //条件组关联方式
            String groupConnection = "AND".equals(conditions.getStr("connection"))?"&&":"||";
            //组内条件表达式
            StringBuilder conditionExp=new StringBuilder("(");
            JSONArray groupsArray = conditions.getJSONArray("groups");
            for(int j=0;j<groupsArray.size();j++){
                if(j!=0){
                    conditionExp.append(groupConnection);
                }
                conditionExp.append("(");
                JSONObject group = groupsArray.get(j,JSONObject.class);
                //条件的关联方式
                String tjConnection= "AND".equals(group.getStr("connection"))?"&&":"||";
                JSONArray conditionArray = group.getJSONArray("condition");
                //条件表达式
                StringBuilder tjExp=new StringBuilder("(");
                for(int k=0;k<conditionArray.size();k++){
                    JSONObject condition = conditionArray.get(k,JSONObject.class);
                    if(k!=0){
                        tjExp.append(tjConnection);
                    }
                    tjExp.append("(");
                    String id = "_fortress_form_data_map."+condition.getStr("id");
                    String symbol = condition.getStr("symbol");
                    String tjType = condition.getStr("type");
                    //数字类型
                    if("number".equals(tjType)){
                        String left = condition.getStr("left");
                        if("between".equals(symbol)){
                            String right = condition.getStr("right");
                            tjExp.append(id).append(">").append(left).append("&&").append(id).append("<").append(right);
                        }else if("between-eq".equals(symbol)){
                            String right = condition.getStr("right");
                            tjExp.append(id).append(">=").append(left).append("&&").append(id).append("<=").append(right);
                        }else{
                            tjExp.append(id).append(symbol).append(left);
                        }
                    //单选或者多选
                    }else if("one".equals(tjType)||"more".equals(tjType)){
                        JSONArray values = condition.getJSONArray("values");
                        if(values!=null && values.size()>0){
                            //完全等于
                            if("==".equals(symbol)){
                                String left= values.toString().substring(1,values.toString().length()-1);
                                tjExp.append(id).append(symbol).append(left);
                                //in 包含在 ${myVariable.contains('hello')}
                            }else{
                                for(int m=0;m<values.size();m++){
                                    if(m!=0){
                                        tjExp.append("||");
                                    }
                                    tjExp.append(id).append(".contains('").append(values.get(m)).append("')");
                                }
                            }
                        }
                    }
                    tjExp.append(")");
                }
                tjExp.append(")");
                conditionExp.append(tjExp).append(")");
            }
            conditionExp.append(")");
            groupConditionExp.append(conditionExp).append("}");
            //条件内节点
            if(conditions.getJSONObject("node")!=null){
                JSONObject nextNode = conditions.getJSONObject("node");
                //条件连接线
                Element sequenceFlow = doc.createElement("sequenceFlow");
                sequenceFlow.setAttribute("id","FLOW_"+exclusiveGatewayId+"_"+nextNode.getStr("id"));
                sequenceFlow.setAttribute("sourceRef",eleGateway.getAttribute("id"));
                sequenceFlow.setAttribute("targetRef","NODE_"+nextNode.getStr("id"));
                Element conditionExpression = doc.createElement("conditionExpression");
                conditionExpression.setTextContent(groupConditionExp.toString());
                sequenceFlow.appendChild(conditionExpression);
                processRoot.appendChild(sequenceFlow);
                String sourceRefId= nextNode.getStr("id");
                while(nextNode!=null){
                    sourceRefId= nextNode.getStr("id");
                    //条件分支
                    if(FlowableNodeTypeEnum.CONDITION.getName().equals(nextNode.getStr("type"))) {
                        createConditionElement(nextNode, processRoot, doc);
                    }else{
                        createElement(nextNode,processRoot,doc);
                    }
                    nextNode=nextNode.getJSONObject("node");
                }
                sourceRefIds.add(sourceRefId);
            }
        }
        //排他网关后会跟空节点，需要跳过
        if(data.getJSONObject("node").getJSONObject("node")!=null){
            JSONObject nextNode = data.getJSONObject("node").getJSONObject("node");
            for(String sourceRefId:sourceRefIds){
                //连接线
                Element sequenceFlow = doc.createElement("sequenceFlow");
                sequenceFlow.setAttribute("id","FLOW_"+sourceRefId+"_"+nextNode.getStr("id"));
                sequenceFlow.setAttribute("sourceRef","NODE_"+sourceRefId);
                sequenceFlow.setAttribute("targetRef","NODE_"+nextNode.getStr("id"));
                processRoot.appendChild(sequenceFlow);
            }
        //结束节点
        }else{
            String endId= "end_"+CommonUtil.getRandomString(17,CommonUtil.RANGE1);
            for(String sourceRefId:sourceRefIds){
                //连接线
                Element sequenceFlow = doc.createElement("sequenceFlow");
                sequenceFlow.setAttribute("id","FLOW_"+sourceRefId+"_"+endId);
                sequenceFlow.setAttribute("sourceRef",sourceRefId);
                sequenceFlow.setAttribute("targetRef",endId);
                processRoot.appendChild(sequenceFlow);
            }
            Element end = doc.createElement("endEvent");
            end.setAttribute("id",endId);
            processRoot.appendChild(end);
        }
        return eleGateway;
    }

    /**
     * 创建结束节点
     * @param preNode
     * @param processRoot
     * @param doc
     * @return
     */
    public static Element createEndElement(Element preNode, Element processRoot,Document doc){
        String endId= "end_"+CommonUtil.getRandomString(17,CommonUtil.RANGE1);
        //连接线
        Element sequenceFlow = doc.createElement("sequenceFlow");
        sequenceFlow.setAttribute("id","FLOW_"+preNode.getAttribute("id")+"_"+endId);
        sequenceFlow.setAttribute("sourceRef",preNode.getAttribute("id"));
        sequenceFlow.setAttribute("targetRef",endId);
        processRoot.appendChild(sequenceFlow);
        Element end = doc.createElement("endEvent");
        end.setAttribute("id",endId);
        processRoot.appendChild(end);
        return end;
    }

    /**
     * 获取flowable表达式参数的名字
     * @param experssName ${_fortress_self_assignee_single_NODE_47170272196}
     * @return
     */
    public static String getParamExpressName(String experssName){
        //todo 正则校验
        return experssName.substring(2,experssName.length()-1);
    }

    /**
     * 插入到流程图的指定位置
     * @param process 流程
     * @param newEle 新节点
     * @param ele 参照节点
     * @param location pre 前 next 后
     */
    public static void insertToProcess(Process process, FlowElement newEle, FlowElement ele, String location){
        List<FlowElement> flowElements = (List<FlowElement>)process.getFlowElements();
        Map<String, FlowElement> flowElementMap = process.getFlowElementMap();
        int insertIndex = 0;
        for(int i=flowElements.size()-1;i>=0;i--){
            if(flowElements.get(i).getId().equals(ele.getId())){
                insertIndex=i;
                break;
            }
        }
        if("pre".equals(location)){
            flowElements.add(insertIndex,newEle);
        }else{
            flowElements.add(insertIndex+1,newEle);
        }
        flowElementMap.put(newEle.getId(),newEle);
    }

//    public static void main(String[] args) {
//        JSONObject a = new JSONObject("{\"id\":\"field103\",\"name\":\"单选框组\",\"type\":\"one\",\"options\":[{\"label\":\"单选项一\",\"value\":1},{\"label\":\"单选项二\",\"value\":2}],\"symbol\":\"=\",\"values\":[11,23]}");
//        System.out.println(a.getStr("id"));
//        JSONArray v = a.getJSONArray("values");
//        System.out.println(v.size());
//        System.out.println(v.toString().substring(1,v.toString().length()-2));
//        System.out.println(v.get(0));
//    }
}
